/**
 * Copyright 2021 Solomon. All rights reserved.
 * Date : 2021.03.24
 */

#include "./inc/spd2010_fw_update.h"

extern const unsigned char mgd_SPD2010_hex[];

/****************************************************/
/* SPD2010_SE_GAMMA_LL_WAIT_HCMD_CLR                */
/****************************************************/
uint8_t SPD2010_SE_GAMMA_LL_WAIT_HCMD_CLR(void)
{
    uint8_t wBuf[2], rBuf[2];
    uint8_t timeout;

    /* Wait HCMD Clear */
    while (1)
    {
        wBuf[0] = (HCMD_ADDRESS & 0xFF);
        wBuf[1] = ((HCMD_ADDRESS >> 8 ) & 0xFF);
        I2C_Write(&wBuf[0], 2);

        nrf_delay_us(50);

        I2C_Read(&rBuf[0], 2);

        timeout++;

        nrf_delay_us(50);

        if ((rBuf[0] == 0x00) && (rBuf[1] == 0x00))
        {
            break;
        }
        if (timeout > 100)
        {
            /* Wait HCMD Clear Failed : timeout */
            // TODO : timeout process
            //return 1;
        }
    }

    return 0;
}

/*******************************/
/* SPD2010_SE_GAMMA_LL_WEN     */
/*******************************/
uint8_t SPD2010_SE_GAMMA_LL_WEN(void)
{
    uint8_t wBuf[4];
    uint8_t ret;

    /* SE_GAMMA_LL_WEN */
    wBuf[0] = (HCMD_ADDRESS & 0xFF);
    wBuf[1] = ((HCMD_ADDRESS >> 8 ) & 0xFF);
    wBuf[2] = 0x30;
    wBuf[3] = 0x02;
    I2C_Write(&wBuf[0], 4);

    ////nrf_delay_ms(1);
    nrf_delay_us(500);

    /* SE_GAMMA_LL_WAIT_HCMD_CLR */
    ret = SPD2010_SE_GAMMA_LL_WAIT_HCMD_CLR();

    return ret;
}

/*******************************/
/* SPD2010_SE_GAMMA_LL_WRSR    */
/*******************************/
uint8_t SPD2010_SE_GAMMA_LL_WRSR(void)
{
    uint8_t wBuf[6];
    uint8_t ret;

    wBuf[0] = (HQARG_ADDRESS & 0xFF);;
    wBuf[1] = ((HQARG_ADDRESS >> 8 ) & 0xFF);
    wBuf[2] = 0x02;
    wBuf[3] = 0x00;
    wBuf[4] = 0x00;
    wBuf[5] = 0x00;
    I2C_Write(&wBuf[0], 6);

    nrf_delay_us(10);

    wBuf[0] = (HCMD_ADDRESS & 0xFF);
    wBuf[1] = ((HCMD_ADDRESS >> 8 ) & 0xFF);
    wBuf[2] = 0x20;
    wBuf[3] = 0x02;
    I2C_Write(&wBuf[0], 4);

    nrf_delay_us(10);

    ret = SPD2010_SE_GAMMA_LL_WAIT_HCMD_CLR();

    return ret;
}

/*******************************/
/* SPD2010_SE_GAMMA_LL_RDSR    */
/*******************************/
uint8_t SPD2010_SE_GAMMA_LL_RDSR(void)
{
    uint8_t wBuf[4], rBuf[2];
    uint8_t timeout;

RETRY_RDSR:
    wBuf[0] = (HCMD_ADDRESS & 0xFF);
    wBuf[1] = ((HCMD_ADDRESS >> 8 ) & 0xFF);
    wBuf[2] = 0x10;
    wBuf[3] = 0x02;
    I2C_Write(&wBuf[0], 4);

    SPD2010_SE_GAMMA_LL_WAIT_HCMD_CLR();

    wBuf[0] = (RHDP_ADDRESS & 0xFF);
    wBuf[1] = ((RHDP_ADDRESS >> 8 ) & 0xFF);
    I2C_Write(&wBuf[0], 2);

    I2C_Read(&rBuf[0], 2);

    if (rBuf[0] & 0x01)
    {
        timeout++;

        nrf_delay_us(5);

        if (timeout > 100)
        {
            /* Failed */
            // TODO : timeout process
            //return 1;
        }

        goto RETRY_RDSR;
    }

    return 0;
}

/*************************************/
/* SPD2010_SE_GAMMA_LL_ERASE_MACRO   */
/*************************************/
uint8_t SPD2010_SE_GAMMA_LL_ERASE_MACRO(void)
{
    uint8_t wBuf[4];
    uint8_t ret;

    /* SE_GAMMA_LL_WEN */
    wBuf[0] = (HCMD_ADDRESS & 0xFF);
    wBuf[1] = ((HCMD_ADDRESS >> 8 ) & 0xFF);
    wBuf[2] = 0x00;
    wBuf[3] = 0x03;
    I2C_Write(&wBuf[0], 4);

    nrf_delay_ms(1);

    /* SE_GAMMA_LL_WAIT_HCMD_CLR */
    ret = SPD2010_SE_GAMMA_LL_WAIT_HCMD_CLR();

    return ret;
}

/*************************************/
/* SPD2010_SE_GAMMA_SL_PROGRAM_PAGE  */
/*************************************/
uint8_t SPD2010_SE_GAMMA_LL_PROGRAM_PAGE(uint32_t addr, uint32_t wlen, uint8_t *fw_data)
{
    uint8_t wbuf[520];
    uint8_t ret;

    /* Write HQARG */
    wbuf[0] = (HQARG_ADDRESS & 0xFF);
    wbuf[1] = ((HQARG_ADDRESS >> 8)& 0xFF);
    wbuf[2] = (addr & 0xFF);
    wbuf[3] = ((addr >> 8) & 0xFF);
    wbuf[4] = ((addr >> 16) & 0xFF);
    wbuf[5] = ((addr >> 24) & 0xFF);
    wbuf[6] = (wlen & 0xFF);
    wbuf[7] = ((wlen >> 8) & 0xFF);
    wbuf[8] = ((wlen >> 16) & 0xFF);
    wbuf[9] = ((wlen >> 24) & 0xFF);
    ret = I2C_Write(&wbuf[0], 10);

    nrf_delay_us(20);

    /* Write HDP */
    wbuf[0] = (RHDP_ADDRESS & 0xFF);
    wbuf[1] = ((RHDP_ADDRESS >> 8)& 0xFF);
    memcpy(&wbuf[2], fw_data, wlen);
    ret = I2C_Write(&wbuf[0], (wlen+2));

    nrf_delay_ms(1);

    /* Write HCMD */
    wbuf[0] = (HCMD_ADDRESS & 0xFF);
    wbuf[1] = ((HCMD_ADDRESS >> 8)& 0xFF);
    wbuf[2] = 0x20;//0x30;
    wbuf[3] = 0x03;//0x02;
    ret = I2C_Write(&wbuf[0], 4);

    /* SE_GAMMA_LL_WAIT_HCMD_CLR */
    ret = SPD2010_SE_GAMMA_LL_WAIT_HCMD_CLR();

    return ret;
}

/*************************************/
/* SPD2010_SE_GAMMA_LL_READ_DATA     */
/*************************************/
uint8_t SPD2010_SE_GAMMA_LL_READ_DATA(uint32_t addr, uint32_t rlen, uint8_t *rbuf)
{
    uint8_t wbuf[10];

    /* Write HQARG */
    wbuf[0] = (HQARG_ADDRESS & 0xFF);
    wbuf[1] = ((HQARG_ADDRESS >> 8)& 0xFF);
    wbuf[2] = (addr & 0xFF);
    wbuf[3] = ((addr >> 8) & 0xFF);
    wbuf[4] = ((addr >> 16) & 0xFF);
    wbuf[5] = ((addr >> 24) & 0xFF);
    wbuf[6] = (rlen & 0xFF);
    wbuf[7] = ((rlen >> 8) & 0xFF);
    wbuf[8] = ((rlen >> 16) & 0xFF);
    wbuf[9] = ((rlen >> 24) & 0xFF);
    I2C_Write(&wbuf[0], 10);

    nrf_delay_ms(1);

    /* Write HCMD */
    wbuf[0] = (HCMD_ADDRESS & 0xFF);
    wbuf[1] = ((HCMD_ADDRESS >> 8)& 0xFF);
    wbuf[2] = 0x30;
    wbuf[3] = 0x03;
    I2C_Write(&wbuf[0], 4);

    nrf_delay_ms(1);

    /* SE_GAMMA_LL_WAIT_HCMD_CLR */
    SPD2010_SE_GAMMA_LL_WAIT_HCMD_CLR();

    nrf_delay_ms(1);

    /* Read HDP */
    wbuf[0] = (RHDP_ADDRESS & 0xFF);
    wbuf[1] = ((RHDP_ADDRESS >> 8)& 0xFF);
    I2C_Write(&wbuf[0], 2);

    nrf_delay_us(20);

    I2C_Read(&rbuf[0], rlen);

    return 0;
}

/*************************************/
/* SPD2010_SE_GAMMA_SL_PROGRAM_PAGE  */
/*************************************/
uint8_t SPD2010_SE_GAMMA_SL_PROGRAM_PAGE(uint32_t addr, uint32_t wlen, uint8_t *fw_data)
{
    SPD2010_SE_GAMMA_LL_WEN();

    nrf_delay_us(10);

    SPD2010_SE_GAMMA_LL_PROGRAM_PAGE(addr, wlen, fw_data);

    nrf_delay_us(10);

    SPD2010_SE_GAMMA_LL_RDSR();

    return 0;
}

/*************************************/
/* SPD2010_SE_GAMMA_SL_READ_DATA     */
/*************************************/
uint8_t SPD2010_SE_GAMMA_SL_READ_DATA(uint32_t addr, uint32_t rlen, uint8_t *rbuf)
{
    SPD2010_SE_GAMMA_LL_READ_DATA(addr, rlen, rbuf);
    return 0;
}

/*******************************/
/* Write_TP_Bios_Cmd      */
/*******************************/
void Write_TP_Bios_Cmd(void)
{
    uint8_t sample_data[4];
    int16_t retry = 500;

READ_SL:
    Read_TP_Status_Length();

    if (TP_Sta_Len.Status_H.TIC_BUSY && retry>0)
    {
      nrf_delay_us(100000);
      retry--;
      goto READ_SL;
    }
    
    if (retry<=0)
    {
        LOG0("[TP]: bios cmd timeout");
    }
    
    if (TP_Sta_Len.Status_H.TIC_IN_BIOS)
    {
        LOG0("[TP]: already in bios mode");
         return;   //already in bios mode
    }

    sample_data[0] = 0x04;
    sample_data[1] = 0x00;
    sample_data[2] = 0x03;
    sample_data[3] = 0x00;

    I2C_Write(&sample_data[0], sizeof(sample_data));
    nrf_delay_us(200);
    LOG0("[TP]: send bios mode cmd");
}

/*******************************/
/* SPD2010_RDID                */
/*******************************/
uint8_t SPD2010_RDID(void)
{
    uint8_t wBuf[4], rBuf[3];
    uint8_t ret;

    nrf_delay_ms(10);

    LOG0("[TP]:SPD2010_RDID ...\n");

    /* Write HCMD */
    wBuf[0] = (HCMD_ADDRESS & 0xFF);
    wBuf[1] = ((HCMD_ADDRESS >> 8) & 0xFF);
    wBuf[2] = 0x00;
    wBuf[3] = 0x02;
    I2C_Write(&wBuf[0], 4);

    nrf_delay_ms(10);
    LOG0("[TP]:enter HCMD clear\n");
    ret = SPD2010_SE_GAMMA_LL_WAIT_HCMD_CLR();
    LOG0("[TP]:exit HCMD clear\n");
    /* Read HDP */
    wBuf[0] = (RHDP_ADDRESS & 0xFF);
    wBuf[1] = ((RHDP_ADDRESS >> 8)& 0xFF);
    I2C_Write(&wBuf[0], 2);

    nrf_delay_us(20);

    I2C_Read(&rBuf[0], 3);

    LOG3("[TP]:RDID = 0x%02x 0x%02x 0x%02x\n", rBuf[0], rBuf[1], rBuf[2]);
    NRF_LOG_PROCESS();

    return ret;
}

/*******************************/
/* SPD2010_FW_Update_Init      */
/*******************************/
uint8_t SPD2010_FW_Update_Init(void)
{
    uint8_t ret;

    /* SE_GAMMA_LL_WEN */
    ret = SPD2010_SE_GAMMA_LL_WEN();

    nrf_delay_us(5);

    /* SE_GAMMA_LL_WRSR */
    ret = SPD2010_SE_GAMMA_LL_WRSR();

    nrf_delay_us(10);

    /* SE_GAMMA_WAIT_RDY_SR */
    SPD2010_SE_GAMMA_LL_RDSR();

    return ret;
}

/*******************************/
/* SPD2010_Flash_Erase         */
/*******************************/
uint8_t SPD2010_Flash_Erase(void)
{
    uint8_t ret;

    ret = SPD2010_SE_GAMMA_LL_WEN();

    nrf_delay_ms(1);

    ret = SPD2010_SE_GAMMA_LL_ERASE_MACRO();

    nrf_delay_ms(1);

    /* SE_GAMMA_WAIT_RDY_SR */
    SPD2010_SE_GAMMA_LL_RDSR();

    return ret;
}

/*******************************/
/* SPD2010_BIOS_Write_FW       */
/*******************************/
uint8_t SPD2010_BIOS_Write_FW(uint8_t part_idx)
{
    uint32_t i, loop_i, wlen, addr, bytecnt;
    uint8_t wbuf[FLASH_W_SIZE];
    const unsigned char *fwdata;

    fwdata = &mgd_SPD2010_hex[FWInfo.FW_Offset[part_idx]];
    bytecnt = FW_Part[part_idx].byte_cnt;

    for (loop_i = 0; loop_i < bytecnt; loop_i += FLASH_W_SIZE)
    {
        addr = FW_Part[part_idx].write_address + loop_i;

        if ((bytecnt - loop_i) > FLASH_W_SIZE)
        {
            wlen = FLASH_W_SIZE;
        }
        else
        {
            wlen = (bytecnt - loop_i);
        }

        for (i = 0; i < wlen; i++)
        {
            wbuf[i] = fwdata[loop_i + i];
        }

        /* Write data to TP Flash */
        SPD2010_SE_GAMMA_SL_PROGRAM_PAGE(addr, wlen, wbuf);
    }

    LOG1("[TP]:SPD2010_BIOS_Write_FW[%d] ... Done\n", part_idx);
    NRF_LOG_PROCESS();

    return 0;
}

/*******************************/
/* SPD2010_BIOS_Read_FW       */
/*******************************/
uint8_t SPD2010_BIOS_Read_FW(uint8_t part_idx)
{
    uint8_t rbuf[FLASH_R_SIZE];
    uint32_t i, loop_k;
    uint32_t addr, bytecnt, rlen;
    const unsigned char *fwdata;

    fwdata = &mgd_SPD2010_hex[FWInfo.FW_Offset[part_idx]];
    bytecnt = FW_Part[part_idx].byte_cnt;

    for (loop_k = 0; loop_k < bytecnt; loop_k += FLASH_R_SIZE)
    {
        addr = FW_Part[part_idx].write_address + loop_k;

        if ((bytecnt - loop_k) >= FLASH_R_SIZE)
        {
            rlen = FLASH_R_SIZE;
        }
        else
        {
            rlen = (bytecnt - loop_k);
        }

        /* Read data form TP flash */
        SPD2010_SE_GAMMA_SL_READ_DATA(addr, rlen, rbuf);

        /* Verify data */
        for (i = 0; i < rlen; i++)
        {
            if (rbuf[i] != fwdata[loop_k + i])
            {
                /* Data verify result is failded */
                LOG5("[TP]:SPD2010_BIOS_Read_FW[%d] : Failed, rbuf[%d] =0x%x,  fwdata[%d] = 0x%x\n"
                             ,part_idx, i, rbuf[i], loop_k + i, fwdata[loop_k + i]);
                return 1;
            }
        }
    }

    LOG1("[TP]:SPD2010_BIOS_Read_FW[%d] ... Done\n", part_idx);

    return 0;
}

/*******************************/
/* SPD2010_Download_FW         */
/*******************************/
uint8_t SPD2010_Download_FW(uint8_t part_idx)
{
    /* Write SPD2010 FW */
    SPD2010_BIOS_Write_FW(part_idx);
    return 0;
}

/*******************************/
/* SPD2010_Verify_FW           */
/*******************************/
uint8_t SPD2010_Verify_FW(uint8_t part_idx)
{
    /* Read & Compare SPD2010 FW */
    SPD2010_BIOS_Read_FW(part_idx);
    return 0;
}

/*******************************/
/* SPD2010_FW_Update           */
/*******************************/
uint8_t SPD2010_FW_Update(void)
{
    uint8_t i;

    /* FW Parser */
    SPD2010_MGD_Parse();

    /* HW Reset */
    TP_Reset();

    /* Check Bios mode */
    Write_TP_Bios_Cmd();
    
    /* Read ID */
    SPD2010_RDID();
    nrf_delay_us(10);

    /* Init */
    SPD2010_FW_Update_Init();
    nrf_delay_us(10);

    /* Flash Erase */
    SPD2010_Flash_Erase();
    nrf_delay_us(10);

    /* Download FW : Low Addr -> High Addr */
    for (i = 0; i < FWInfo.total_part_num; i++)
    {
        SPD2010_Download_FW(FWInfo.order[i]);
    }

    nrf_delay_ms(100);

    /* Verify FW : Low Addr -> High Addr */
    for (i = 0; i < FWInfo.total_part_num; i++)
    {
        SPD2010_Verify_FW(FWInfo.order[i]);
    }

    /* TP HW Reset */
    TP_Reset();

    return 0;
}
